package com.baijiaoxi.common.model;

import com.baijiaoxi.common.model.base.CoreException;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static com.baijiaoxi.common.model.base.CommonError.dataNotExistOrExpired;
import static com.baijiaoxi.common.model.base.CommonError.notSupport;
import static java.lang.System.currentTimeMillis;

public interface KeyValueManager {

    default long getTimeout() {
        return -1;
    }

    default String getPrefix() {
        return "";
    }

    default String getRealKey(String key) {
        return getPrefix() + key;
    }

    boolean hasKey(String key);

    default boolean hasKeySilent(String key) {
        return hasKey(key);
    }

    Object get(String key);

    default Object getSilent(String key) {
        return get(key);
    }

    default Object getNoThrow(String key) {
        try {
            return get(key);
        } catch (Exception e) {
            return null;
        }
    }

    boolean set(String key, Object value, long validSeconds);

    default boolean setSilent(String key, Object value, long validSeconds) {
        return set(key, value, validSeconds);
    }

    boolean set(String key, Object value);

    default boolean setSilent(String key, Object value) {
        return set(key, value);
    }

    boolean updateExpireTime(String key, Long validSeconds);


    Set<String> keys(String pattern);


    void del(String... key);

    class DataValue {
        protected Object originData;
        protected long expireTime;


        public DataValue(Object originData, long validSeconds) {
            this.originData = originData;
            this.expireTime = validSeconds > 0 ? (currentTimeMillis() + (validSeconds * 1000)) : -1;
        }

        public Object getData() {
            return originData;
        }

        public void setData(Object data) {
            this.originData = data;
        }

        public long getExpireTime() {
            return expireTime;
        }

        public boolean addExpireTime(long addValidSeconds) {
            long currentTimeMillis = System.currentTimeMillis();
            if (this.expireTime <= currentTimeMillis)
                return false;
            this.expireTime = this.expireTime + (addValidSeconds * 1000);
            return true;
        }
    }

    @Slf4j
    class DefaultManger implements KeyValueManager {
        private Map<String, DataValue> dataMap = new ConcurrentHashMap<>();

        protected String prefix;
        protected long timeout = -1;

        public DefaultManger() {
        }

        public DefaultManger(String prefix) {
            this.prefix = prefix;
        }

        public DefaultManger(String prefix, long timeout) {
            this.prefix = prefix;
            this.timeout = timeout;
        }

        @Override
        public String getPrefix() {
            return this.prefix;
        }

        @Override
        public long getTimeout() {
            return this.timeout;
        }

        @Override
        public boolean hasKey(String key) {
            String realKey = getRealKey(key);
            if (dataMap.containsKey(realKey)) {
                DataValue dataValue = dataMap.get(getRealKey(realKey));
                return dataValue.getExpireTime() > System.currentTimeMillis() || dataValue.getExpireTime() == -1;
            }
            return false;
        }

        @Override
        public Object get(String key) {
            if (hasKey(key)) {
                return dataMap.get(getRealKey(key));
            } else
                throw CoreException.create(dataNotExistOrExpired, key);
        }

        @Override
        public boolean set(String key, Object value, long validSeconds) {
            try {
                dataMap.put(getRealKey(key), new DataValue(value, validSeconds));
            } catch (Exception e) {
                return false;
            }
            return true;
        }

        @Override
        public boolean set(String key, Object value) {
            try {
                dataMap.put(getRealKey(key), new DataValue(value, getTimeout()));
            } catch (Exception e) {
                return false;
            }
            return true;
        }

        @Override
        public boolean updateExpireTime(String key, Long validSeconds) {
            try {
                DataValue dataValue = dataMap.get(getRealKey(key));
                if (dataValue == null)
                    return false;
                return dataValue.addExpireTime(validSeconds);
            } catch (Exception e) {
                return false;
            }
        }

        @Override
        public Set<String> keys(String pattern) {
            throw CoreException.create(notSupport, "默认方法是简单，轻量的，不实现此方法");
        }

        @Override
        public void del(String... keys) {
            Arrays.stream(keys).parallel().forEach(key -> {
                try {
                    dataMap.remove(getRealKey(key));
                } catch (Exception e) {
                    log.error("移除键值时发生异常", e);
                }
            });
        }
    }

}
